package com.ht.guice.jooq;

import org.jooq.ConnectionProvider;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.jooq.tools.JooqLogger;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Savepoint;

/**
 * Created by bcw on 14-4-3.
 */
public class TxDataSourceConnectionProvider implements ConnectionProvider {
    private static final JooqLogger log = JooqLogger.getLogger(DSL.class);
    private final DataSource dataSource;
    private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
    private ThreadLocal<Boolean> txActive = new ThreadLocal<>();

    public TxDataSourceConnectionProvider(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public DataSource dataSource() {
        return dataSource;
    }

    @Override
    public Connection acquire() {
        if (isOpened()) {// 事务已打开
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                return connection;
            } else {
                throw new DataAccessException("Error getting connection from tx manager, transaction is opened but connection is removed");
            }

        } else {// 事务未打开，直接返回连接
            try {
                return dataSource.getConnection();
            } catch (SQLException e) {
                throw new DataAccessException("Error getting connection from data source " + dataSource, e);
            }
        }
    }

    @Override
    public void release(Connection connection) {
        if (!isOpened()) {// 只有在事务未打开时才可以主动关闭连接
            try {
                connection.close();
            } catch (SQLException e) {
                throw new DataAccessException("Error closing connection " + connection, e);
            }
        }
    }

    public void open() {
        txActive.set(true);
        try {
            Connection connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            connectionThreadLocal.set(connection);
        } catch (SQLException e) {
            throw new DataAccessException("Error getting connection from data source " + dataSource, e);
        }
    }

    public boolean isOpened() {
        return txActive.get() != null;
    }

    public void close() {
        txActive.remove();
        Connection connection = connectionThreadLocal.get();
        if (connection != null) {
            connectionThreadLocal.remove();
            try {
                connection.close();
            } catch (SQLException e) {
                throw new DataAccessException("Error closing connection " + connection, e);
            }
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#commit()}.
     */
    public final void commit() throws DataAccessException {
        try {
            log.debug("commit");
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.commit();
            }
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot commit transaction", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#rollback()}.
     */
    public final void rollback() throws DataAccessException {
        try {
            log.debug("rollback");
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.rollback();
            }
            throw new DataAccessException("Cannot rollback, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot rollback transaction", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#rollback(java.sql.Savepoint)}.
     */
    public final void rollback(Savepoint savepoint) throws DataAccessException {
        try {
            log.debug("rollback to savepoint");
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.rollback(savepoint);
            }
            throw new DataAccessException("Cannot rollback to savepoint, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot rollback transaction", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#setSavepoint()}.
     */
    public final Savepoint setSavepoint() throws DataAccessException {
        try {
            log.debug("set savepoint");
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                return connection.setSavepoint();
            }
            throw new DataAccessException("Cannot set savepoint, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot set savepoint", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#setSavepoint(String)}.
     */
    public final Savepoint setSavepoint(String name) throws DataAccessException {
        try {
            log.debug("set savepoint", name);
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                return connection.setSavepoint(name);
            }
            throw new DataAccessException("Cannot set savepoint, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot set savepoint", e);
        }
    }

    /**
     * Convenience method to access
     * {@link java.sql.Connection#releaseSavepoint(java.sql.Savepoint)}.
     */
    public final void releaseSavepoint(Savepoint savepoint) throws DataAccessException {
        try {
            log.debug("release savepoint");
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.releaseSavepoint(savepoint);
            }
            throw new DataAccessException("Cannot release savepoint, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot release savepoint", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#setAutoCommit(boolean)}.
     */
    public final void setAutoCommit(boolean autoCommit) throws DataAccessException {
        try {
            log.debug("setting auto commit", autoCommit);
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.setAutoCommit(autoCommit);
            }
            throw new DataAccessException("Cannot setting auto commit, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot set autoCommit", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#getAutoCommit()}.
     */
    public final boolean getAutoCommit() throws DataAccessException {
        try {
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.getAutoCommit();
            }
            throw new DataAccessException("Cannot get auto commit, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot get autoCommit", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#setHoldability(int)}.
     */
    public final void setHoldability(int holdability) throws DataAccessException {
        try {
            log.debug("setting holdability", holdability);
            acquire().setHoldability(holdability);
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.setHoldability(holdability);
            }
            throw new DataAccessException("Cannot setting holdability, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot set holdability", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#getHoldability()}.
     */
    public final int getHoldability() throws DataAccessException {
        try {
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                return connection.getHoldability();
            }
            throw new DataAccessException("Cannot get holdability, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot get holdability", e);
        }
    }

    /**
     * Convenience method to access
     * {@link java.sql.Connection#setTransactionIsolation(int)}.
     */
    public final void setTransactionIsolation(int level) throws DataAccessException {
        try {
            log.debug("setting tx isolation", level);
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.setTransactionIsolation(level);
            }
            throw new DataAccessException("Cannot set tx isolation, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot set transactionIsolation", e);
        }
    }

    /**
     * Convenience method to access {@link java.sql.Connection#getTransactionIsolation()}.
     */
    public final int getTransactionIsolation() throws DataAccessException {
        try {
            Connection connection = connectionThreadLocal.get();
            if (connection != null) {
                connection.getTransactionIsolation();
            }
            throw new DataAccessException("Cannot get tx isolation, connection is closed");
        }
        catch (Exception e) {
            throw new DataAccessException("Cannot get transactionIsolation", e);
        }
    }
}
